#ifndef BMD_ENGINE_H
#define BMD_ENGINE_H

#include <vector>
#include "stage.h"
#include <fstream>

using namespace std;


class VictoryConditions
{
private:

	ScoringProperties victory_condition;
	ActiveProperties active_condition;
	bool winner_exists;

	void init(const ScoringProperties& in_v_condition, const ActiveProperties& in_a_condition)
	{	victory_condition = in_v_condition;
		active_condition = in_a_condition;
		winner_exists = false;}

public:
	//check if player won or is eliminated
	bool check_for_WinElim(const ScoringProperties& player_score)
	{
		if(active_condition.deaths && player_score.deaths >= victory_condition.deaths)
			return false;

		if(active_condition.kills && player_score.kills >= victory_condition.kills)
			winner_exists = true;
		if(active_condition.score && player_score.score >= victory_condition.score)
			winner_exists = true;
		return true;
	}
};


class Engine
{

private:
	bool first_use; //used for reseting
	Stage stage;

	VictoryConditions v_conditions;

	int max(int a, int b)
	{
		if(a > b)
			return a;
		return b;
	}

	int create_bomb_id()
	{
		if(bombs.size() > 0)
			return max(bombs.back().get_id()+1,bombs.size());
		return 0;
	}
	/*
		adds a new bomb to the "bombs" vector with the given ID and location
	*/
	void create_bomb(	const Coordinates & bomb_coords, 
						const unsigned int & bomber_id, 
						const BombProperties & in_prop, 
						const int fresh_id)
	{
		Bomb new_bomb;

		new_bomb.set(fresh_id, bomber_id, in_prop); //ADD DURATION
		new_bomb.coords = bomb_coords;

		bombs.push_back(new_bomb);
	}

	int create_bomber_id()
	{
		if(bombers.size() > 0)
			return max(bombers.back().get_id()+1,bombers.size());
		return 0;
	}
	/*
		add a new bomber
	*/
	void create_bomber(const Coordinates & bomber_coords, 
					   const int fresh_id)
	{
		Bomber new_bomber(fresh_id);

		new_bomber.coords = bomber_coords;

		bombers.push_back(new_bomber);
	}

	void create_powerup(PowerUp & in_powerup)
	{
		int fresh_id;
		if(powerups.size() > 0)
			fresh_id = max(powerups.back().id+1,powerups.size());
		else
			fresh_id = 0;
		in_powerup.id = fresh_id;
		powerups.push_back(in_powerup);
	}


	//void bomb_explosion(Bomb& bomb, unsigned int starter_id)
	void bomb_explosion(Bomb& bomb, vector<Bomber>::iterator start_bomber)
	{
		ExplosionResult temp_result;
		int owner_id;
		vector<int>::iterator x_iter;
		vector<Bomber>::iterator x_bomber;
		vector<Bomb>::iterator x_bomb;
		vector<PowerUp>::iterator c_powerup;

		//use bomb.gone - pack all this into a function in engine - this way no iterator messiness
		//
		//
		//

		bomb.gone = true;
		owner_id = bomb.get_owner_id();

		// @see Bomber::notify_explosion
        bombers[owner_id].notify_explosion(bomb.get_id());

		x_bomber = bombers.begin();
		while(x_bomber != bombers.end()) 
		{		
			if(x_bomber->get_id() == owner_id)
			{
					x_bomber->nbombs--;
					break; //ids are unique
			}
				
			x_bomber++;
		}


		temp_result = stage.detonate_bomb(bomb.coords, bomb.prop.power);
		
		
				
		for(x_iter = temp_result.ef_bombers.begin();
			x_iter != temp_result.ef_bombers.end();
			x_iter++)
		{
			//this finds the next bomber in the list
			x_bomber = bombers.begin();
			while(x_bomber != bombers.end() && x_bomber->get_id() != *x_iter) {x_bomber++;}
			//add kill if on different teams - for now always
			//bombers[*(x_iter)].kill(); //bomber_id always corresponds to vector index
			//bombers[starter_id].score.kills++;
			if(x_bomber != bombers.end())
			{
				x_bomber->kill();
				if(x_bomber->mstate.is_displaced()) // CHANGE
					stage.bomber_InformTileClear(x_bomber->mstate.reserved(x_bomber->coords));
				//add tile wipe code here

				start_bomber->score.kills++;

                                // tell the bomber it died
                                x_bomber->notify_death();

                                // tell the teammate that its teammate died
                                int team_id = x_bomber->get_team_id();
                                // find a teammate and notify death
                                for (unsigned int i = 0; i < bombers.size(); ++i)
                                {
                                  if (bombers[i].get_team_id() == team_id &&
                                      bombers[i].get_id() != x_bomber->get_id() &&
                                      bombers[i].is_alive())
                                  {
                                    bombers[i].notify_teammate_death();
                                  }
                                }
			}
		}

		for(c_powerup = temp_result.created_powerups.begin();
			c_powerup < temp_result.created_powerups.end();
			c_powerup++)
		{
			create_powerup(*c_powerup);
			stage.place_powerup(*c_powerup);
		}

		
		for(x_iter = temp_result.ef_bombs.begin();
			x_iter != temp_result.ef_bombs.end();
			x_iter++)
		{
			//this finds the next bomb in the list, second condition should ALWAYS be true
			x_bomb = bombs.begin();
			while(x_bomb != bombs.end() && x_bomb->get_id() != *x_iter) {x_bomb++;} // CHANGE
			//stage.detonate_bomb_bomb(bombs[*(x_iter)].coords, bomb[*(x_iter)].prop.power);
			if(x_bomb != bombs.end() && x_bomb->get_id() != bomb.get_id()) // CHANGE - safety to preven infinte recursion
			{
				bomb_explosion(*x_bomb, start_bomber);
			}
		}
		
		Explosion temp_explosion(temp_result.explosion_data);
		explosions.push_back(temp_explosion);
	}

public:

	vector<Bomber> bombers;
	vector<Bomb> bombs;
	vector<PowerUp> powerups;
	vector<Explosion> explosions;

	struct team_dat
	{
		int id;
		int team_id;
	};

	int nPlayers;
	vector<string> agent_file_names;
	vector<team_dat> team_data;
	

	//
	//implement a 'real' init once map-flies are decided etc.
	//

	//performs one time step of the game
	void execute_time_step()
	{
		Coordinates temp_coords;
		int temp_id;
		
		vector<Bomber>::iterator bomber;
		vector<Bomb>::iterator bomb;
		vector<PowerUp>::iterator powerup;
		vector<Explosion>::iterator explosion;
		//bool el_removed; //was an element removed from the vector this iteration

		VMInput vm_input;
		VMBomber temp_vm_bomber;
		VMBomb temp_vm_bomb;
		VMPowerUp temp_vm_pup;
		MoveResult temp_move_result;
                
		/*************************** ignore this

		Here lies the virtual machine.

		****************************/

                for (unsigned int i = 0; i < bombers.size(); ++i)
                {
                  if (bombers[i].is_alive() && !bombers[i].mstate.is_displaced())
                  {
                    // request an order and place 
                    // the result in the bomber orders
                    bombers[i].request_orders(construct_vm_input(bombers[i].get_id()));
                  }
                }

		bomber = bombers.begin();
                
		for(; bomber != bombers.end(); bomber++)
		{
			if(bomber->is_alive())
			{
				if(bomber->orders.move_ordered())
				{
					if(!(bomber->mstate.change_direction(bomber->orders.move_direction)))
					{}
				}
				if (bomber->orders.detonate) // CHANGE
				{
					if(bomber->prop.detonation)
					{
						temp_id = bomber->get_id();
						bomb = bombs.begin();
						while(bomb != bombs.end())
						{
							if(bomb->get_owner_id() == temp_id)
								bomb->orders.detonate = true;
							bomb++; // CHANGE
						}
					}
				}
				if (bomber->orders.bomb_place)
				{
					if(bomber->bombs_available())
					{
						temp_id = create_bomb_id(); // CHANGE
						if(stage.place_bomb(*bomber,temp_id)) // CHANGE
						//if(stage.place_bomb(bomber->coords, (unsigned int)bombs.size())) //it is known that the ID will be bombs.size()
						{
							create_bomb(bomber->coords,bomber->id,bomber->bomb_prop,temp_id); //if the bomb is successfully placed, actually create it
							bomber->nbombs++;
						}
						else
						{} //nothing for now, maybe send a message back?
					}
					else
					{} //nothing for now, maybe send a message back?
				}


				

				if(bomber->mstate.is_displaced())
				{
					if(bomber->mstate.tick())  //did this 'tick' (time frame) cause a tile change?
					{
						temp_coords = bomber->mstate.dest(bomber->coords);
						bomber->mstate.perform_tile_change(); //updates the mstate, NOT coordinates
						stage.bomber_InformTileChange(bomber->get_id(),bomber->coords,temp_coords); //changes reservation data
						bomber->coords = temp_coords; //update coordinates
					}
					if(! bomber->mstate.is_displaced()) //if the last tick caused the bomber to be square on a tile
					{
						stage.bomber_InformTileClear(bomber->mstate.source(bomber->coords));
					}
				}
				else //bomber is sitting on a tile right now
				{
					if(bomber->mstate.moving()) //this unneccessarily checks displacement
					{
						temp_coords = bomber->mstate.dest(bomber->coords); //find the destination of movement
						temp_move_result = stage.bomber_CheckMove(temp_coords); // CHANGED
						if (temp_move_result.success()) //can a bomber move to the location?
						{
							stage.bomber_UnsafeCommenceMove(temp_coords); //perform neccessary reservations
							//the following code should be identical to the one above
							if(bomber->mstate.tick())  //did this 'tick' (time frame) cause a tile change?
							{
								temp_coords = bomber->mstate.dest(bomber->coords);
								bomber->mstate.perform_tile_change(); //updates the mstate, NOT coordinates
								stage.bomber_InformTileChange(bomber->get_id(),bomber->coords,temp_coords); //changes reservation data
								bomber->coords = temp_coords; //update coordinates
							}
							if(! bomber->mstate.is_displaced()) //if the last tick caused the bomber to be square on a tile
							{
								stage.bomber_InformTileClear(bomber->mstate.source(bomber->coords));
							}
						}
						else if (temp_move_result.get_type() == mrtBOMB)
						{
							if(bomber->prop.kicking) //can the bomber "kick" bombs?
							{
								if(bombs.size() > 0)
								{
									bomb = bombs.begin();
									while(bomb != bombs.end())
									{
										if(bomb->get_id() == temp_move_result.get_collidee_id())
											break;
										else
											bomb++;
									}

									//bomb now points to the bomb to be kicked
									if(bomb->mstate.is_displaced())
										stage.bomb_InformTileClear(bomb->mstate.reserved(bomb->coords));
									 //if bomb isn't already kicked in that direction
									if(bomb->mstate.get_direction() != bomber->mstate.get_direction())
										bomb->mstate.reset(bomber->mstate.get_direction()); //the bomb is now centered on the tile and kicked

								}
								// there is no else - it would be a bug
								

							}
						}
					}
				}


				//check if current tile has a powerup, give it to bomber
				temp_id = stage.acquire_powerup(bomber->coords);
				if(temp_id >= 0) //an actual PowerUp acquired
				{
					powerup = powerups.begin();
					while( powerup != powerups.end())
					{
						if(powerup->id == temp_id)
						{
							bomber->gain_powerup(powerup->power_type_index);
							powerup = powerups.erase(powerup);
							continue;
						}
						powerup++;
					}
				}


			} //end if( is alive)
		}
                
                
                
                
		bomb = bombs.begin();
		while(bomb != bombs.end())
		{
			if(!bomb->gone)
			{
			//countdown, movement - decide how detonation is handled then code
			if (bomb->orders.detonate || bomb->tick()) //did this bomb tick cause the bomb to detonate, was there an order?
			{
				bomber = bombers.begin();
				while(bomber != bombers.end() && bomber->get_id() != bomb->get_owner_id()) bomber++;

				if(bomber != bombers.end())
				{
					bomb_explosion(*bomb, bomber);
					//bomb = bombs.erase(bomb); //don't erase bombs until later
					//continue; //to avoid bomb++ // CHANGE
				}
				//this point should never be reached - place break for debugging
			}
			else //if bomb still exists, do its mstate tick (mstate is movement state)
			{
				if(bomb->mstate.is_displaced())
				{
					if(bomb->mstate.tick())  //did this 'tick' (time frame) cause a tile change?
					{
						temp_coords = bomb->mstate.dest(bomb->coords);
						bomb->mstate.perform_tile_change(); //updates the mstate, NOT coordinates
						stage.bomb_InformTileChange(bomb->get_id(),bomb->coords,temp_coords); //changes reservation data
						bomb->coords = temp_coords; //update coordinates
					}
					if(! bomb->mstate.is_displaced()) //if the last tick caused the bomb to be square on a tile
					{
						stage.bomb_InformTileClear(bomb->mstate.source(bomb->coords));
					}
				}
				else //bomb is sitting on a tile right now
				{
					if(bomb->mstate.moving()) //this unneccessarily checks displacement
					{
						temp_coords = bomb->mstate.dest(bomb->coords); //find the destination of movement
						if (stage.bomb_CheckMove(temp_coords).success()) //can a bomb move to the location?
						{
							stage.bomb_UnsafeCommenceMove(temp_coords); //perform neccessary reservations
							//the following code should be identical to the one above
							if(bomb->mstate.tick())  //did this 'tick' (time frame) cause a tile change?
							{
								temp_coords = bomb->mstate.dest(bomb->coords);
								bomb->mstate.perform_tile_change(); //updates the mstate, NOT coordinates
								stage.bomb_InformTileChange(bomb->get_id(),bomb->coords,temp_coords); //changes reservation data
								bomb->coords = temp_coords; //update coordinates
							}
							if(! bomb->mstate.is_displaced()) //if the last tick caused the bomb to be square on a tile
							{
								stage.bomb_InformTileClear(bomb->mstate.source(bomb->coords));
							}
						}
						else
						{
							//the bomb collided with something, stop its movement
							bomb->mstate.change_direction(dVOID);
						}
					}
				}
			}
			} //end if(!bomb->gone)
			bomb++;
		}
		
		//the following while loop removes any bombs not initially removed
		bomb = bombs.begin();
		while(bomb != bombs.end()) // CHANGE cosmetic
		{
			if(bomb->gone)
			{
				stage.remove_bomb_residue(bomb->coords);
				if(bomb->mstate.is_displaced()) // CHANGE
					stage.bomb_InformTileClear(bomb->mstate.reserved(bomb->coords));
				bomb = bombs.erase(bomb);
			}
			else
				bomb++;
		}

		
		powerup = powerups.begin();

		//for(powerup; powerup != powerups.end(); powerup++)
		{
			//expiration time?
		}

		
		explosion = explosions.begin();
		while(explosion != explosions.end())
		{
			if(explosion->tick())
			{
				for(unsigned int i = 1; i <= explosion->explosion_data.dW; i++)
				{
					temp_coords.x = explosion->explosion_data.center.x - i;
					temp_coords.y = explosion->explosion_data.center.y;
					stage.clear_explosion(temp_coords);
				}
					temp_coords.x = explosion->explosion_data.center.x - (explosion->explosion_data.dW + 1);
					temp_coords.y = explosion->explosion_data.center.y;
					stage.clear_explosion_brick(temp_coords);

				for(unsigned int i = 1; i <= explosion->explosion_data.dE; i++)
				{
					temp_coords.x = explosion->explosion_data.center.x + i;
					temp_coords.y = explosion->explosion_data.center.y;
					stage.clear_explosion(temp_coords);
				}
					temp_coords.x = explosion->explosion_data.center.x + (explosion->explosion_data.dE + 1);
					temp_coords.y = explosion->explosion_data.center.y;
					stage.clear_explosion_brick(temp_coords);

				for(unsigned int i = 1; i <= explosion->explosion_data.dS; i++)
				{
					temp_coords.x = explosion->explosion_data.center.x;
					temp_coords.y = explosion->explosion_data.center.y + i;
					stage.clear_explosion(temp_coords);
				}
					temp_coords.x = explosion->explosion_data.center.x;
					temp_coords.y = explosion->explosion_data.center.y + (explosion->explosion_data.dS + 1);
					stage.clear_explosion_brick(temp_coords);

				for(unsigned int i = 1; i <= explosion->explosion_data.dN; i++)
				{
					temp_coords.x = explosion->explosion_data.center.x;
					temp_coords.y = explosion->explosion_data.center.y - i;
					stage.clear_explosion(temp_coords);
				}
					temp_coords.x = explosion->explosion_data.center.x;
					temp_coords.y = explosion->explosion_data.center.y - (explosion->explosion_data.dN + 1);
					stage.clear_explosion_brick(temp_coords);

				explosion = explosions.erase(explosion);
				continue; //to avoid increment
			}
			explosion++;
		}
		//stage.ascii_dump(); //testing code
	}
        
        // constructs a value to send to the vm
        Value construct_vm_input(int bomber_id) const;

	int get_tile_tindex(unsigned int& i, unsigned int& j)
	{
		Coordinates temp(i,j);
		return stage.tile_tinfo(temp);
	}

	bool check_tile_index(unsigned int& i, unsigned int& j, int tindex)
	{
		Coordinates temp(i,j);
		return stage.check_tinfo(temp,tindex);
	}

	unsigned int getHeight(){return stage.getH();}
	unsigned int getWidth(){return stage.getW();}


	////////////////////////
	////////////////////////
	////////////////////////
	/// Testing Code follows
	////////////////////////

	



	/*
	file:

	nPlayers
	<agent file name> <team number>
	<agent file name> <team number>
	...

        team numbers need to be positive
	*/
	void init_player_data()
	{
		fstream fin(fname_PLAYER_DATA);
		string temp_agent_file;
		int temp_team_id;
		team_dat temp_team_dat;

		if(!fin.eof())
			fin >> nPlayers;

		for(int i = 0; i < nPlayers; i++)
		{
			if(!fin.eof())
			{
				fin >> temp_agent_file;
				fin >> temp_team_id;
				
				temp_team_dat.id = i;
				temp_team_dat.team_id = temp_team_id;

				agent_file_names.push_back(temp_agent_file);
				team_data.push_back(temp_team_dat);

                                Bomber temp_bomber(i);
                                temp_bomber.team_id = temp_team_id;
                                //	temp_bomber.coords = *i_pcoords;
                                bombers.push_back(temp_bomber);

                                try
                                {
                                  // VM loading
                                  bombers[i].actor->load_from_file(temp_agent_file);
                                }
                                catch (runtime_error e)
                                {
                                  // nothing catastrophic for now...
                                  cerr << "unsuccessful loading of " << temp_agent_file
                                       << endl << e.what() << endl;
                                }
                                
			}
			else
				break;
		}


		
		fin.close();
                
                // DONE: inserted vm initialization
                
                // now assign actor links based on team number
                for (int i = 0; i < nPlayers; ++i)
                {
                  for (int j = 0; j < nPlayers; ++j)
                  {
                    if (i == j) continue;

                    // if bombers are on the same team, link them
                    if (bombers[i].team_id == bombers[j].team_id)
                    {
                      bombers[i].actor->set_linked_actor(bombers[j].actor);
                      bombers[j].actor->set_linked_actor(bombers[i].actor);
                    }
                  }
                }

                for (int i = 0; i < nPlayers; ++i)
                  bombers[i].call_main();
	}



	void init()
	{
		stage.test_init();

	}

	void test_init()
	{
		init_player_data(); //get number of players

		vector<Coordinates> player_coords;
		vector<Coordinates>::iterator i_pcoords;

		player_coords = stage.test_init(); //send number of players
		i_pcoords = player_coords.begin();

		for(int i = 0; i_pcoords != player_coords.end(); i++,i_pcoords++)
		{
                  bombers[i].coords = *i_pcoords;
		}
                
                for (int i = 0; i < bombers.size(); ++i)
                  bombers[i].call_init();
	}
};

#endif
